package logger

import (
	"fmt"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
	"os"
	"path/filepath"
	"sync"
	"time"
)

type connom struct {
	logFile    string
	lumberjack *lumberjack.Logger
	l          *zap.Logger
}

type loggerMethods struct {
	c     connom
	mutex sync.RWMutex
}

type ZapAppLogger struct {
	l loggerMethods
}

type ZapGormLogger struct {
	l loggerMethods
}

type ZapHttpLogger struct {
	l loggerMethods
}
type ZapGrpcLogger struct {
	l loggerMethods
}

var zapConfig = zapcore.EncoderConfig{
	MessageKey: "message",
	TimeKey:    "timestamp",
	LevelKey:   "level",
	CallerKey:  "caller",
	EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
		enc.AppendString(t.Format("2006-01-02 15:04:05"))
	},
	EncodeLevel:  zapcore.CapitalLevelEncoder,
	EncodeCaller: zapcore.ShortCallerEncoder,
}

func NewGormLogger() *ZapGormLogger {
	c := newZapLogger("gorm")
	return &ZapGormLogger{
		l: loggerMethods{c: c},
	}
}

func NewHttpLogger() *ZapHttpLogger {
	c := newZapLogger("http")
	return &ZapHttpLogger{
		l: loggerMethods{c: c},
	}
}

func NewAppLogger() *ZapAppLogger {
	c := newZapLogger("app")

	return &ZapAppLogger{
		l: loggerMethods{c: c},
	}
}

func NewGrpcLogger() *ZapAppLogger {
	c := newZapLogger("grpc")

	return &ZapAppLogger{
		l: loggerMethods{c: c},
	}
}

func newZapLogger(logFile string) connom {

	filePathStr := filePath(logFile)

	lumberjackLogger := &lumberjack.Logger{
		Filename:   filePathStr,
		MaxSize:    128,
		MaxBackups: 300,
		MaxAge:     30,
		Compress:   true,
		LocalTime:  true,
	}

	encoder := zapcore.NewConsoleEncoder(zapConfig)
	zapWriter := zapcore.AddSync(lumberjackLogger)
	zapCore := zapcore.NewCore(encoder, zapWriter, zap.InfoLevel)

	logger := zap.New(zapCore)

	return connom{
		logFile:    logFile,
		lumberjack: lumberjackLogger,
		l:          logger,
	}
}

func (a *ZapAppLogger) Debug(msg string, args ...Field) {
	a.l.writeLog(zap.DebugLevel, msg, args...)
}

func (a *ZapAppLogger) Info(msg string, args ...Field) {
	a.l.writeLog(zap.InfoLevel, msg, args...)
}

func (a *ZapAppLogger) Warn(msg string, args ...Field) {
	a.l.writeLog(zap.WarnLevel, msg, args...)
}

func (a *ZapAppLogger) Error(msg string, args ...Field) {
	a.l.writeLog(zap.ErrorLevel, msg, args...)
}

func (g *ZapGormLogger) Info(msg string, args ...Field) {
	g.l.writeLog(zap.InfoLevel, msg, args...)
}

func (h *ZapHttpLogger) Info(msg string, args ...Field) {
	h.l.writeLog(zap.InfoLevel, msg, args...)
}

func (g *ZapGrpcLogger) Info(msg string, args ...Field) {
	g.l.writeLog(zap.InfoLevel, msg, args...)
}

func (a *ZapGrpcLogger) Warn(msg string, args ...Field) {
	a.l.writeLog(zap.WarnLevel, msg, args...)
}

func (lm *loggerMethods) writeLog(level zapcore.Level, msg string, args ...Field) {
	lm.checkAndCreateNewLogFile()
	// 使用 With 方法附加字段
	logEntry := lm.c.l.With(lm.toZapFields(args)...)
	// 根据日志级别调用相应的方法
	switch level {
	case zapcore.DebugLevel:
		logEntry.Debug(msg)
	case zapcore.InfoLevel:
		logEntry.Info(msg)
	case zapcore.WarnLevel:
		logEntry.Warn(msg)
	case zapcore.ErrorLevel:
		logEntry.Error(msg)
	default:
		logEntry.Info(msg)
	}
}

func (lm *loggerMethods) checkAndCreateNewLogFile() {
	lm.mutex.RLock()
	defer lm.mutex.RUnlock()

	logFilePath := filePath(lm.c.logFile)
	if logFilePath != lm.c.lumberjack.Filename {
		lm.c.lumberjack.Close()
		lm.c.lumberjack = &lumberjack.Logger{
			Filename:   logFilePath,
			MaxSize:    128,
			MaxBackups: 300,
			MaxAge:     30,
			Compress:   true,
			LocalTime:  true,
		}
		lm.c.l = zap.New(zapcore.NewCore(zapcore.NewConsoleEncoder(zapConfig), zapcore.AddSync(lm.c.lumberjack), zap.InfoLevel))

	}
}

func (lm *loggerMethods) toZapFields(args []Field) []zap.Field {
	res := make([]zap.Field, 0, len(args))
	for _, arg := range args {
		res = append(res, zap.Any(arg.Key, arg.Val))
	}
	return res
}

func filePath(fileName string) string {
	now := time.Now()
	yearMonth := now.Format("2006-01")
	day := now.Format("02")
	logDir := fmt.Sprintf("../runtime/log/%s/%s", yearMonth, day)
	logFileName := fmt.Sprintf("%s.log", fileName)
	logFilePath := filepath.Join(logDir, logFileName)
	if err := os.MkdirAll(logDir, 0777); err != nil {
		fmt.Printf("Error creating directory: %v\n", err)
		return ""
	}
	_, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777)
	if err != nil {
		panic(err)
	}
	return logFilePath
}
